﻿#region << 版 本 注 释 >>
/*----------------------------------------------------------------
 * 版权所有 (c) 2022 北京超维景生物科技有限公司 保留所有权利。
 * CLR版本：4.0.30319.42000
 * 文件名：Kernel
 * 
 * 创建者：huangyang
 * 电子邮箱：huangyang@tvscope.cn
 * 创建时间：2023/2/8 15:26:11
 * 版本：V1.0.0
 * 描述：
 *
 * ----------------------------------------------------------------
 * 修改人：
 * 时间：
 * 修改说明：
 *
 * 版本：V1.0.1
 *----------------------------------------------------------------*/
#endregion << 版 本 注 释 >>

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using ImageK.Java;
using ImageK.Java.Extensions;
using ImageK.Plugin.Frame;
using ImageK.Process;

namespace ImageK.IO
{
    /**
     * Opens or reverts an image specified by a FileInfo object. Images can
     * be loaded from either a file (directory+fileName) or a URL (url+fileName).
     * Here is an example:	
     * <pre>
     *   public class FileInfo_Test implements PlugIn {
     *     public void run(String arg) {
     *       FileInfo fi = new FileInfo();
     *       fi.width = 256;
     *       fi.height = 254;
     *       fi.offset = 768;
     *       fi.fileName = "blobs.tif";
     *       fi.directory = "/Users/wayne/Desktop/";
     *       new FileOpener(fi).open();
     *     }  
     *   }	
     * </pre> 
     */
    public class FileOpener
    {

        private FileInfo _fi;
        private int _width, _height;
        private static bool _showConflictMessage = true;
        private double _minValue, _maxValue;
        private static bool _silentMode;

        public FileOpener(FileInfo fi)
        {
            this._fi = fi;
            if (fi != null)
            {
                _width = fi.width;
                _height = fi.height;
            }
            // if (IJ.debugMode) IJ.log("FileInfo: " + fi);
        }

        /** Opens the image and returns it as an ImagePlus object. */
        public ImagePlus openImage()
        {
            bool wasRecording = Recorder.OnRecord;
            Recorder.OnRecord = false;
            ImagePlus imp = open(false);
            Recorder.OnRecord = wasRecording;
            return imp;
        }

		/** Obsolete, replaced by openImage() and open(). */
		public ImagePlus open(bool show)
		{

			ImagePlus imp = null;
			object pixels;
			ProgressBar pb = null;
			ImageProcessor ip;

			ColorModel cm = createColorModel(_fi);
			if (_fi.nImages > 1)
				return openStack(cm, show);

            //todo:
			switch (_fi.fileType)
			{
				case FileInfo.GRAY8:
				case FileInfo.COLOR8:
				case FileInfo.BITMAP:
					pixels = readPixels(_fi);
					if (pixels == null) return null;
					ip = new ByteProcessor(_width, _height, (byte[])pixels, cm);
					imp = new ImagePlus(_fi.fileName, ip);
					break;
				case FileInfo.GRAY16_SIGNED:
				case FileInfo.GRAY16_UNSIGNED:
				case FileInfo.GRAY12_UNSIGNED:
					pixels = readPixels(_fi);
					if (pixels == null) return null;
					ip = new ShortProcessor(_width, _height, (short[])pixels, cm);
					imp = new ImagePlus(_fi.fileName, ip);
					break;
			// 	case FileInfo.GRAY32_INT:
			// 	case FileInfo.GRAY32_UNSIGNED:
			// 	case FileInfo.GRAY32_FLOAT:
			// 	case FileInfo.GRAY24_UNSIGNED:
			// 	case FileInfo.GRAY64_FLOAT:
			// 		pixels = readPixels(fi);
			// 		if (pixels == null) return null;
			// 		ip = new FloatProcessor(width, height, (float[])pixels, cm);
			// 		imp = new ImagePlus(fi.fileName, ip);
			// 		break;
			// 	case FileInfo.RGB:
			// 	case FileInfo.BGR:
			// 	case FileInfo.ARGB:
			// 	case FileInfo.ABGR:
			// 	case FileInfo.BARG:
			// 	case FileInfo.RGB_PLANAR:
			// 	case FileInfo.CMYK:
			// 		pixels = readPixels(fi);
			// 		if (pixels == null) return null;
			// 		ip = new ColorProcessor(width, height, (int[])pixels);
			// 		if (fi.fileType == FileInfo.CMYK)
			// 			ip.invert();
			// 		imp = new ImagePlus(fi.fileName, ip);
			// 		break;
			// 	case FileInfo.RGB48:
			// 	case FileInfo.RGB48_PLANAR:
			// 		boolean planar = fi.fileType == FileInfo.RGB48_PLANAR;
			// 		Object[] pixelArray = (Object[])readPixels(fi);
			// 		if (pixelArray == null) return null;
			// 		int nChannels = 3;
			// 		ImageStack stack = new ImageStack(width, height);
			// 		stack.addSlice("Red", pixelArray[0]);
			// 		stack.addSlice("Green", pixelArray[1]);
			// 		stack.addSlice("Blue", pixelArray[2]);
			// 		if (fi.samplesPerPixel == 4 && pixelArray.length == 4)
			// 		{
			// 			stack.addSlice("Gray", pixelArray[3]);
			// 			nChannels = 4;
			// 		}
			// 		imp = new ImagePlus(fi.fileName, stack);
			// 		imp.setDimensions(nChannels, 1, 1);
			// 		if (planar)
			// 			imp.getProcessor().resetMinAndMax();
			// 		imp.setFileInfo(fi);
			// 		int mode = IJ.COMPOSITE;
			// 		if (fi.description != null)
			// 		{
			// 			if (fi.description.indexOf("mode=color") != -1)
			// 				mode = IJ.COLOR;
			// 			else if (fi.description.indexOf("mode=gray") != -1)
			// 				mode = IJ.GRAYSCALE;
			// 		}
			// 		imp = new CompositeImage(imp, mode);
			// 		if (!planar && fi.displayRanges == null)
			// 		{
			// 			if (nChannels == 4)
			// 				((CompositeImage)imp).resetDisplayRanges();
			// 			else
			// 			{
			// 				for (int c = 1; c <= 3; c++)
			// 				{
			// 					imp.setPosition(c, 1, 1);
			// 					imp.setDisplayRange(minValue, maxValue);
			// 				}
			// 				imp.setPosition(1, 1, 1);
			// 			}
			// 		}
			// 		if (fi.whiteIsZero) // cmyk?
			// 			IJ.run(imp, "Invert", "");
			// 		break;
			}
			imp.setFileInfo(_fi);
			// setCalibration(imp);
			// if (fi.info != null)
			// 	imp.setProperty("Info", fi.info);
			// if (fi.sliceLabels != null && fi.sliceLabels.length == 1 && fi.sliceLabels[0] != null)
			// 	imp.setProp("Slice_Label", fi.sliceLabels[0]);
			// if (fi.plot != null) try
			// 	{
			// 		Plot plot = new Plot(imp, new ByteArrayInputStream(fi.plot));
			// 		imp.setProperty(Plot.PROPERTY_KEY, plot);
			// 	}
			// 	catch (Exception e) { IJ.handleException(e); }
			// if (fi.roi != null)
			// 	decodeAndSetRoi(imp, fi);
			// if (fi.overlay != null)
			// 	setOverlay(imp, fi.overlay);
			// if (fi.properties != null)
			// 	imp.setProperties(fi.properties);
			// if (show) imp.show();
			return imp;
		}

        /** Opens a stack of images. */
        ImagePlus openStack(ColorModel cm, bool show)
        {
            ImageStack stack = new ImageStack(_fi.width, _fi.height, cm);
            long skip = _fi.getOffset();
            object pixels;
            try
            {
                ImageReader reader = new ImageReader(_fi);
                InputStream rs = createInputStream(_fi);
                if (rs == null)
                    return null;
                IJ.resetEscape();
                for (int i = 1; i <= _fi.nImages; i++)
                {
                    if (!_silentMode)
                        IJ.ShowStatus("Reading: " + i + "/" + _fi.nImages);
                    if (IJ.escapePressed())
                    {
                        IJ.beep();
                        IJ.showProgress(1.0);
                        _silentMode = false;
                        return null;
                    }
                    pixels = reader.readPixels(rs, skip);
                    if (pixels == null)
                        break;
                    stack.addSlice(null, pixels);
                    skip = _fi.getGap();
                    if (!_silentMode)
                        IJ.showProgress(i, _fi.nImages);
                }
                rs.Close();
            }
            catch (OutOfMemoryException e)
            {
                IJ.outOfMemory(_fi.fileName);
                stack.trim();
            }
            catch (Exception e)
            {
                IJ.log("" + e);
            }

            if (!_silentMode) IJ.showProgress(1.0);
            if (stack.size() == 0)
                return null;
            if (_fi.sliceLabels != null && _fi.sliceLabels.Length <= stack.size())
            {
                for (int i = 0; i < _fi.sliceLabels.Length; i++)
                    stack.setSliceLabel(_fi.sliceLabels[i], i + 1);
            }
            ImagePlus imp = new ImagePlus(_fi.fileName, stack);
            //todo:
            // if (_fi.info != null)
            //     imp.setProperty("Info", fi.info);
            // if (_fi.roi != null)
            //     decodeAndSetRoi(imp, fi);
            // if (_fi.overlay != null)
            //     setOverlay(imp, fi.overlay);
            // if (_fi.properties != null)
            //     imp.setProperties(fi.properties);
            // if (show) imp.show();
            // imp.setFileInfo(fi);
            // setCalibration(imp);
            // ImageProcessor ip = imp.getProcessor();
            // if (ip.getMin() == ip.getMax())  // find stack min and max if first slice is blank
            //     setStackDisplayRange(imp);
            // if (!_silentMode) IJ.showProgress(1.0);
            return imp;
        }


		public static void SetSilentMode(bool mode)
        {
            _silentMode = mode;
        }

        /** Returns an IndexColorModel for the image specified by this FileInfo. */
        public ColorModel createColorModel(FileInfo fi)
        {
            if (fi.lutSize > 0)
                return new IndexColorModel(8, fi.lutSize, fi.reds, fi.greens, fi.blues);
            else
                return LookUpTable.createGrayscaleColorModel(fi.whiteIsZero);
        }

        /** Returns an InputStream for the image described by this FileInfo. */
        public InputStream createInputStream(FileInfo fi) 
        {
            InputStream rs = null;
            bool gzip = fi.fileName != null && (fi.fileName.EndsWith(".gz") || fi.fileName.EndsWith(".GZ"));
            if (fi.inputStream != null)
            {
                rs = fi.inputStream;
            }
            else if (fi.url != null && !fi.url.Equals(""))
            {
                rs = new URL(fi.url + fi.fileName).openStream();
            }
            else {
                if (fi.directory!=null && fi.directory.Length>0 && !(fi.directory.EndsWith(Prefs.separator)||fi.directory.EndsWith("/")))
                    fi.directory += Prefs.separator;


                // FileInfo f = new File(fi.getFilePath());
                System.IO.FileInfo f = new System.IO.FileInfo(fi.getFilePath());

                if (gzip) fi.compression = FileInfo.COMPRESSION_UNKNOWN;

                if (!File.Exists(fi.getFilePath()) || Directory.Exists(fi.getFilePath()) || !validateFileInfo(f, fi))
                {
                    rs = null;
                }
                else
                {
                    //todo:
                    // rs = new FileInputStream(f);
                    rs = new RandomAccessStream(new FileStream(fi.getFilePath(), FileMode.Open));
                }
            }
            if (rs!=null) {
                if (fi.compression>=FileInfo.LZW)
                    rs = new RandomAccessStream(((RandomAccessStream)rs)._src);
                else if (gzip)
                    rs = new GZIPInputStream(rs, 50000);
            }
            return rs;
        }

        static bool validateFileInfo(System.IO.FileInfo f, FileInfo fi)
        {
            long offset = fi.getOffset();
            long length = 0;
            if (fi.width <= 0 || fi.height <= 0)
            {
                error("Width or height <= 0.", fi, offset, length);
                return false;
            }
            if (offset >= 0 && offset < 1000L)
                return true;
            if (offset < 0L)
            {
                error("Offset is negative.", fi, offset, length);
                return false;
            }
            if (fi.fileType == FileInfo.BITMAP || fi.compression != FileInfo.COMPRESSION_NONE)
                return true;
            length = f.Length;
            long size = fi.width * fi.height * fi.getBytesPerPixel();
            size = fi.nImages > 1 ? size : size / 4;
            if (fi.height == 1) size = 0; // allows plugins to read info of unknown length at end of file
            if (offset + size > length)
            {
                error("Offset + image size > file length.", fi, offset, length);
                return false;
            }
            return true;
        }

        static void error(string msg, FileInfo fi, long offset, long length)
        {
            string msg2 = "FileInfo parameter error. \n"
                          + msg + "\n \n"
                          + "  Width: " + fi.width + "\n"
                          + "  Height: " + fi.height + "\n"
                          + "  Offset: " + offset + "\n"
                          + "  Bytes/pixel: " + fi.getBytesPerPixel() + "\n"
                          + (length > 0 ? "  File length: " + length + "\n" : "");
            if (_silentMode)
            {
                IJ.log("Error opening " + fi.getFilePath());
                IJ.log(msg2);
            }
            else
                IJ.error("FileOpener", msg2);
        }

        /** Reads the pixel data from an image described by a FileInfo object. */
        Object readPixels(FileInfo fi)
        {
            Object pixels = null;
            try
            {
                InputStream rs = createInputStream(fi);
                if (rs==null)
                return null;
                ImageReader reader = new ImageReader(fi);
                pixels = reader.readPixels(rs);
                _minValue = reader.min;
                _maxValue = reader.max;

                    rs.Close();
            }
            catch (Exception e)
            {
                if (!Macro.MACRO_CANCELED.Equals(e.Message))
                    IJ.handleException(e);
            }
            return pixels;
        }


        public Dictionary<string, string> decodeDescriptionString(FileInfo fi)
        {
            if (fi.description==null || fi.description.Length<7)
                return null;
            if (IJ.debugMode)
                IJ.log("Image Description: " + new String(fi.description).Replace('\n', ' '));
            if (!fi.description.StartsWith("ImageJ"))
                return null;
            Dictionary<string, string> props = new Dictionary<string, string>();
            props.load(fi.description);
            // InputStream is = new ByteArrayInputStream(fi.description.getBytes());
            // try { props.load(is); is.close(); }
            // catch (IOException e) { return null; }
            string dsUnit = props.getProperty("unit", "");
            if ("cm".Equals(fi.unit) && "um".Equals(dsUnit))
            {
                fi.pixelWidth *= 10000;
                fi.pixelHeight *= 10000;
            }
            fi.unit = dsUnit;
            double n = getNumber(props, "cf");
            if (n!=null) fi.calibrationFunction = (int)n;
            double[] c = new double[5];
            int count = 0;
            for (int i = 0; i<5; i++)
            {
                n = getNumber(props, "c"+i);
                if (n==null) break;
                c[i] = n;
                count++;
            }
            if (count>=2)
            {
                fi.coefficients = new double[count];
                for (int i = 0; i<count; i++)
                    fi.coefficients[i] = c[i];
            }
            fi.valueUnit = props.getProperty("vunit");
            n = getNumber(props, "images");
            if (n!=null && n>1.0)
                fi.nImages = (int)n;
            n = getNumber(props, "spacing");
            if (n!=null)
            {
                double spacing = n;
                if (spacing<0) spacing = -spacing;
                fi.pixelDepth = spacing;
            }
            String name = props.getProperty("name");
            if (name!=null)
                fi.fileName = name;
            return props;
        }

        private double getNumber(Dictionary<string, string> props, String key)
        {
            String s = props[key];
            if (s!=null)
            {
                try
                {
                    return Convert.ToDouble(s);
                }
                catch (FormatException e) { }
            }
            return double.NaN;
        }

        private double getDouble(Dictionary<string, string> props, String key)
        {
            double n = getNumber(props, key);
            return n!=null ? n : 0.0;
        }

        private bool getBoolean(Dictionary<string,string> props, string key)
        {
            String s = props[key];
            return s!=null&&s.Equals("true") ? true : false;
        }

        public static void setShowConflictMessage(bool b)
        {
            _showConflictMessage = b;
        }

        static void setSilentMode(bool mode)
        {
            _silentMode = mode;
        }
    }
}
